package cn.zyq.charging.order.webSocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

//用于：每次设备给订单同步充电进度数据，订单模块用OpenFeign调用计费模块计算费用后，将累计的总费用推送到用户端
// websocket服务端测试提示：下载并安装apipost工具创建一个webSocket请求 向 ws://localhost:8080/ws/server/1 发送请求
// 看到order模块控制台提示：WebSocketServer    : onOpen
//                      WebSocketServer    : 用户:1,连接成功:0   就是运行正常
@Slf4j
@Component
@ServerEndpoint(value = "/ws/server/{userId}")// value表示websocket给哪个用户的客户端发送信息
public class WebSocketServer {
    /**
     * ConcurrentHashMap sessions用于存储每个用户的session对象。
     * 给哪个用户发送信息就从集合中获取哪个用户的session对象。
     * 线程安全的map JUC
     * 用来存放 每个用户对应的session 会话状态
     * key是用户id   -   value是会话session
     *
     * 这种方式： 是将session存储到本地ConcurrentHashMap 在内存里存储的,
     * 缺点：重启后,数据就清空了,session清空了。 清空后会导致session丢失 用户和服务端的连接断开了。
     *       还有如果部署了多个订单服务，则多个map之间的数据不共享，也会导致问题。
     * 持久化, 需要持久化的压力(如果数据太多， 持久化就太慢了)
     *
     * 用websocket可以实现客服的聊天室
     * https://blog.csdn.net/xpfjllmm/article/details/137624364 redis持久化方案
     * https://www.51cto.com/article/785408.html 持久化
     */
    private static ConcurrentHashMap<Integer, Session>  sessions = new ConcurrentHashMap<>();
    //后边可以用数据库或者redis将sessions集合的session数据进行持久化，这样可以让多个订单服务共享同一个Map集合中的session。//////

    /**
     * 连接建立成功调用的方法  类似于 mqtt connectComplete
     * @param session 会话状态
     * @param userId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") Integer userId){
        log.info("onOpen");
        sessions.put(userId,session);
        log.info("用户:{},连接成功:{}", userId, session.getId());
    }

    /**
     * session 连接关闭
     * @param session
     * @param userId
     */
    @OnClose
    public void onClose(Session session, @PathParam("userId") Integer userId) {
        sessions.remove(userId);
        log.debug("用户{},退出",userId);
    }

    /**
     * 客户端给服务端发消息执行本方法：
     * 当服务端接收到消息的时候执行本方法 (客户端给服务端发消息)
     * @param message 具体接收到的消息
     * @param session 谁发送的(哪个客户端发送的消息)
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.debug("用户:{},发送消息:{}",session,message);
    }

    /**
     * 出现问题时调用此方法  具体错误的原因用throwable封装 ， 根据session可以推断哪个客户端出现问题
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("onError", throwable);
    }

    /**
     * 服务端给客户端发消息执行本方法(可以通过apiPost客户端工具来模拟客户端接收本方法发送的消息)：
     * 发送消息 服务端主动推送消息给客户端
     * @param userId
     * @param message
     */
    public void sendMessage(Integer userId, String message){
        Session session = sessions.get(userId);
        if (session != null) {//如果session不为空表示有用户连接了WebSocketServer(有用户可以接受消息)
            try {
                log.debug("WebSocketServer:sendMessage():用户:{},发送消息:{}",userId,message);
                session.getBasicRemote().sendText(message);
                //session.getAsyncRemote().sendText(message);//发送异步的消息(本项目没用这个方法)
                //Asynchronous 是异步的意思
                //synchronous 是同步的意思
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }else {
            log.error("用户:{},已经下线",userId);
        }
    }
}
